package com.itheima.es.springboot.test01;

import com.alibaba.fastjson.JSON;
import com.itheima.es.springboot.pojo.TbSku;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.event.annotation.AfterTestMethod;
import org.springframework.test.context.event.annotation.BeforeTestMethod;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @author 雪落
 * @date 2020/12/31 - 19:23
 */
@SpringBootTest
public class Es_springboot {
    //客户端对象
    private RestHighLevelClient client;

    /**
     * 建立连接
     */
    @BeforeEach
    public void init() throws IOException {
        //创建Rest客户端
        client = new RestHighLevelClient(
                RestClient.builder(
                        //如果是集群，则设置多个主机，注意端口是http协议的端口
                        new HttpHost("192.168.200.150", 9200, "http")

//                        ,new HttpHost("localhost", 9201, "http")
//                        ,new HttpHost("localhost", 9202, "http")
                )
        );
    }

    /**
     * 查询所有
     *
     * @throws IOException
     */
    @Test
    public void testBasicSearch() throws IOException {
        System.out.println(client);
        // 1.创建SearchSourceBuilder对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //  1.1.添加查询条件QueryBuilders，这里选择match_all，查询所有
        sourceBuilder.query(
                QueryBuilders.matchAllQuery()
        );
        //  1.2.添加排序、分页等其它条件(暂忽略)

        // 2.创建SearchRequest对象，并指定索引库名称
        SearchRequest request = new SearchRequest("userppp");
        // 3.添加SearchSourceBuilder对象到SearchRequest对象中
        request.source(sourceBuilder);
        // 4.发起请求，得到结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 5.解析结果
        SearchHits searchHits = response.getHits();
        //  5.1.获取总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("total = " + total);
        //  5.2.获取SearchHit数组，并遍历
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            //获取分数
            System.out.println("文档得分：" + hit.getScore());
            //  - 获取其中的`_source`，是JSON数据
            String json = hit.getSourceAsString();
            //  - 把`_source`反序列化为User对象
            TbSku user = JSON.parseObject(json, TbSku.class);
            System.out.println("user = " + user);
        }
    }

    /**
     * 词条查询termQuery-不分词
     *
     * @throws Exception
     */
    @Test
    public void testTermQuery() throws Exception {
        //1. 创建SearchSourceBuilder对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //   1. 添加查询条件QueryBuilders.termQuery()
        sourceBuilder.query(
                QueryBuilders.termQuery("name", "眼镜")
        );
//        sourceBuilder.query(QueryBuilders.termQuery("note", "黑马"));
        //2. 创建SearchRequest对象，并制定索引库名称
        SearchRequest request = new SearchRequest("userppp");
        //3. 添加SearchSourceBuilder对象到SearchRequest对象source中
        request.source(sourceBuilder);
        //4. 发起请求，得到结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //5. 解析结果SearchResponse
        SearchHits searchHits = response.getHits();
        //   1. 获取总条数
        System.out.println("总记录数：" + searchHits.getTotalHits().value);
        //   2. 获取SearchHits数组，并遍历
        for (SearchHit searchHit : searchHits) {
            //      * 获取其中的`_source`，是JSON数据
            String userJson = searchHit.getSourceAsString();
            //      * 把`_source`反序列化为User对象
            TbSku user = JSON.parseObject(userJson, TbSku.class);
            System.out.println(user);
        }
    }

    /**
     * 抽取通用构建查询条件执行查询方法
     *
     * @throws Exception
     */
    public void printResultByQuery(QueryBuilder queryBuilder) throws Exception {
        //1. 创建SearchSourceBuilder对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //   ************ 构建查询条件************
        sourceBuilder.query(queryBuilder);

        //排序（看情况添加）
        sourceBuilder.sort("id", SortOrder.DESC);


        // ***** 设置分页 from size
        int page = 2; // 当前页
        int size = 3; // 一页显示条数
        int from = (page - 1) * size; // 每一页起始条数
        sourceBuilder.from(from);
        sourceBuilder.size(size);


        //2. 创建SearchRequest对象，并制定索引库名称
        SearchRequest request = new SearchRequest("userppp");
        //3. 添加SearchSourceBuilder对象到SearchRequest对象source中
        request.source(sourceBuilder);
        //4. 发起请求，得到结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //5. 解析结果SearchResponse
        SearchHits searchHits = response.getHits();
        //   1. 获取总条数
        System.out.println("总记录数：" + searchHits.getTotalHits().value);
        //   2. 获取SearchHits数组，并遍历
        for (SearchHit searchHit : searchHits) {
            //      * 获取其中的`_source`，是JSON数据
            //获取的是非高亮
            String userJson = searchHit.getSourceAsString();
            //      * 把`_source`反序列化为User对象
            TbSku user = JSON.parseObject(userJson, TbSku.class);
            System.out.println(user);
        }

    }

    private void parseResponse(SearchSourceBuilder sourceBuilder) throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("userppp");
        // 2.准备参数
        request.source(sourceBuilder);
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        SearchHits searchHits = response.getHits();
        // 4.1.总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("total = " + total);
        //   2. 获取SearchHits数组，并遍历
        for (SearchHit searchHit : searchHits) {
            //      * 获取其中的`_source`，是JSON数据
            //获取的是非高亮
            String userJson = searchHit.getSourceAsString();
            //      * 把`_source`反序列化为User对象
            TbSku user = JSON.parseObject(userJson, TbSku.class);
            //获取高亮
            //因为获取到的是对象，所以他自动用map集合存储
            Map<String, HighlightField> map = searchHit.getHighlightFields();
            if (map != null && !map.isEmpty()) {
                //根据高亮字段名字获取高亮值
                HighlightField name = map.get("name");
                //获取片段
                Text[] fragments = name.getFragments();
                //拼接片段得到完整的高亮结果
                /**  StringBuilder sb = new StringBuilder();
                 for (Text fragment : fragments) {
                 sb.append(fragment.toString());
                 }
                 //这种被代替了(apache的lang3)
                 */
                String join = StringUtils.join(fragments);
                user.setName(join);
            }
            System.out.println(user);
        }
    }

    /**
     * 匹配查询MatchQuery 对条件进行分词
     *
     * @throws Exception
     */
    @Test
    public void testMatchQuery() throws Exception {
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "眼镜");
        printResultByQuery(queryBuilder);
    }

    /**
     * 模糊查询FuzzyQuery 对查询条件可以修正
     *
     * @throws Exception
     */
    @Test
    public void testFuzzyQuery() throws Exception {
        FuzzyQueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("name", "眼进");
        queryBuilder.fuzziness(Fuzziness.TWO);
        printResultByQuery(queryBuilder);
    }

    /**
     * 条件查询 + 排序
     *
     * @throws Exception
     */
    @Test
    public void testRangeQuery() throws Exception {
        RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery("num");
        // 22 <= age < 27
        queryBuilder.gte(10000);
        queryBuilder.lt(12000);

        printResultByQuery(queryBuilder);
    }

    /**
     * QueryString 多条件查询
     *
     * @throws Exception
     */
    @Test
    public void testQueryStringQuery() throws Exception {
        QueryStringQueryBuilder queryBuilder =
                QueryBuilders.queryStringQuery("黑马同学")
                        .field("name")
                        .field("note")
//                .defaultField("note")  // 默认搜索域
                        .defaultOperator(Operator.AND);
        printResultByQuery(queryBuilder);
    }

    /**
     * 匹配查询BoolQuery 布尔查询+过滤
     *
     * @throws Exception
     */
    @Test
    public void testBoolQuery1() throws Exception {
        // 1.构建bool条件对象
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 2.构建matchQuery对象,查询相信信息`note`为: 黑马
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("note", "黑马");
        queryBuilder.must(matchQueryBuilder);

        // 3.过滤姓名`name`包含：小武
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "小武");
        queryBuilder.filter(termQueryBuilder);

        // 4.过滤年龄`age`在：23-27
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").gte(23).lte(27);
        queryBuilder.filter(rangeQueryBuilder);

        printResultByQuery(queryBuilder);
    }

    /**
     * 匹配查询BoolQuery 布尔查询（链式编程）
     *
     * @throws Exception
     */
    @Test
    public void testBoolQuery2() throws Exception {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.matchQuery("noto", "唱歌"))
                        .mustNot(QueryBuilders.rangeQuery("age").gte(30))
        );
    }

    /**
     * 布尔查询 分页
     *
     * @throws Exception
     */
    @Test
    public void testBoolQueryByPage() throws Exception {
        // 1.构建bool条件对象
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 2.构建matchQuery对象,查询相信信息`note`为: 黑马
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "老花");
        queryBuilder.must(matchQueryBuilder);

        printResultByQuery(queryBuilder);
    }

    /**
     * 高亮显示
     */
    @Test
    public void testHighlightQuery() throws Exception {
        //条件工厂
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("name", "眼镜"))
                //高亮
                .highlighter(new HighlightBuilder().field("name"));
        //发起请求，解析响应
        parseResponse(searchSourceBuilder);
    }

    /**
     * es核心：聚合
     * 桶分类
     * 分组查询
     */
    @Test
    public void ElasticSearchAggsTest() throws Exception {
        // 1.创建SearchRequest对象，并制定索引库名称
        SearchRequest searchRequest = new SearchRequest("car");

        //2.创建SearchSourceBuilder对象，设置分组相关参数
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //添加多个聚合条件
        sourceBuilder.aggregation(
                AggregationBuilders
                        .terms("popular_color")
                        .field("color.keyword"));
        // 分页参数
        sourceBuilder.size(0);

        // 3.添加SearchSourceBuilder对象到SearchRequest对象source中
        searchRequest.source(sourceBuilder);
        // 4.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        // 5.得到查询结果
        SearchHits responseHits = response.getHits();
        //获取总条数
        TotalHits totalHits = responseHits.getTotalHits();
        System.out.println("total："+totalHits.value);

        // 6.解析分组查询数据
        Terms terms = (Terms) response.getAggregations().asMap().get("popular_color");
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKey()+" : "+ bucket.getDocCount());
        }
    }
    /**
     * 桶聚合分组查询
     * @throws Exception
     */
    @Test
    public void testAggsAvgTest() throws Exception{

        // 1.创建SearchRequest对象，并制定索引库名称
        SearchRequest searchRequest = new SearchRequest("car");

        //2.创建SearchSourceBuilder对象，设置分组相关参数
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // ***设置分组参数
        TermsAggregationBuilder popularColorAggs = AggregationBuilders.terms("popular_color").field("color.keyword");

        // ***分组后平均价格计算
        AvgAggregationBuilder priceAggs = AggregationBuilders.avg("avg_price").field("price");
        // ***按颜色分组后添加到子聚合计算
        popularColorAggs.subAggregation(priceAggs);

        sourceBuilder.aggregation(popularColorAggs);
        // 分页参数
        sourceBuilder.size(0);

        // 3.添加SearchSourceBuilder对象到SearchRequest对象source中
        searchRequest.source(sourceBuilder);
        // 4.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        // 5.得到查询结果
        SearchHits responseHits = response.getHits();
        TotalHits totalHits = responseHits.getTotalHits();
        System.out.println("total："+totalHits.value);


        // 6.解析分组查询数据
        Terms terms = (Terms) response.getAggregations().asMap().get("popular_color");
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            // ***解析分组数据
            System.out.println(bucket.getKey()+" : "+ bucket.getDocCount());

            // ***解析平均价格
            ParsedAvg parsedAvg = (ParsedAvg) bucket.getAggregations().getAsMap().get("avg_price");
            System.out.println(bucket.getKey()+" 平均价格：" + parsedAvg.getValue());
        }
    }

    /**
     * 关闭客户端连接
     */
    @AfterEach
    public void close() throws IOException {
        client.close();
    }
}
